﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using WindowsAPI.Hooks;
using WindowsAPI.Processes;
using WindowsAPI.Service;

namespace WindowsAPI.Windows
{
    /// <summary>Класс взаимодействия с окном Windows</summary>
    public partial class Window : IWin32Window, ITreeItem<Window>
    {
        /* --------------------------------------------------------------------------------------------- */

        private static bool EnumChildsWindows(IntPtr hWnd, IntPtr lParam)
        {
            var lv_ListHandleTemp = GCHandle.FromIntPtr(lParam);
            var lv_HandlesTemp = lv_ListHandleTemp.Target as List<IntPtr>;
            if(lv_HandlesTemp == null)
                throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
            lv_HandlesTemp.Add(hWnd);
            return true;
        }

        /* --------------------------------------------------------------------------------------------- */

        /// <summary>Идентификатор потока</summary>
        private readonly int f_ThreadId;

        /// <summary>Идентификатор процесса</summary>
        private readonly int f_ProcessId;

        /// <summary>Положение в Z-последовательности</summary>
        private readonly LazyValue<ZWindowsOrder> f_ZOrder;

        /// <summary>Описание класса окна</summary>
        private readonly LazyValue<WindowClass> f_WindowClass;

        /* --------------------------------------------------------------------------------------------- */

        /// <summary>Дескриптор окна</summary>
        public IntPtr Handle { get; private set; }

        /// <summary>Заголовок окна</summary>
        public string Text
        {
            get { return GetWindowText(); }
            set { if(!SetWindowText(value)) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); }
        }

        /// <summary>Название класса окна</summary>
        public string ClassName { get { return GetWindowClass(); } }

        public ZWindowsOrder ZOrder { get { return f_ZOrder.Value; } }

        public SystemMenu SystemMenu { get { return SystemMenu.Create(this); } }

        public Window Parent { get { return f_ZOrder.Value.Parent; } set { throw new NotSupportedException(); } }

        public Window Child { get { return f_ZOrder.Value.Child; } set { throw new NotSupportedException(); } }

        public Window Next { get { return f_ZOrder.Value.Next; } set { throw new NotSupportedException(); } }

        public Window Prev { get { return f_ZOrder.Value.Prev; } set { throw new NotSupportedException(); } }

        public Window[] Childs
        {
            get
            {
                var lv_Handles = new List<IntPtr>();
                var lv_ListHandle = GCHandle.Alloc(lv_Handles);
                try
                {
                    User32.EnumChildWindows(Handle, EnumChildsWindows, GCHandle.ToIntPtr(lv_ListHandle));
                } finally
                {
                    if(lv_ListHandle.IsAllocated) lv_ListHandle.Free();
                }

                return lv_Handles.ConvertAll(hWnd => new Window(hWnd)).ToArray();
            }
        }

        public Rectangle Rectangle
        {
            get
            {
                var lv_Rect = new RECT();
                if(!User32.GetWindowRect(Handle, ref lv_Rect))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                return lv_Rect;
            }
            set
            {
                if(!User32.MoveWindow(Handle, value.Left, value.Top, value.Width, value.Height, true))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

        /// <summary>Положение левого верхнего угла окна</summary>
        public Point Location { get { return Rectangle.Location; } set { Rectangle = new Rectangle(value, Rectangle.Size); } }

        /// <summary>Положение левого верхнего угла окна по горизонтали</summary>
        public int X { get { return Location.X; } set { Location = new Point(value, Location.Y); } }
        /// <summary>Положение левого верхнего угла окна по вертикали</summary>
        public int Y { get { return Location.Y; } set { Location = new Point(Location.X, value); } }

        /// <summary>Размеры окна</summary>
        public Size Size { get { return Rectangle.Size; } set { Rectangle = new Rectangle(Location, value); } }

        /// <summary>ШИрина окна</summary>
        public int Width { get { return Rectangle.Width; } set { Size = new Size(value, Height); } }
        /// <summary>Высота окна</summary>
        public int Height { get { return Rectangle.Height; } set { Size = new Size(Width, value); } }

        /// <summary>Идентификатор процесса окна</summary>
        public int ProcessId { get { return f_ProcessId; } }
        /// <summary>Идентификатор потока окна</summary>
        public int ThreadId { get { return f_ThreadId; } }

        /// <summary>Процесс окна</summary>
        public Process Process { get { return Process.GetProcessById(f_ProcessId); } }

        public FileInfo FileName { get { return Process.GetProcessFileInfo(); } }

        /// <summary>Дочернее окно по дескриптору</summary>
        /// <param name="Handle">Дескриптор дочернего окна</param>
        /// <returns>Дочернее окно с указанным дескриптором</returns>
        public Window this[IntPtr Handle] { get { return Array.Find(Childs, w => w.Handle == Handle); } }

        /// <summary>Дочернее окно по указанной строке заголовка</summary>
        /// <param name="Text">ТЕкст заголовка окна</param>
        /// <returns>Дочернее окно с указанным заголовком</returns>
        public Window this[string Text] { get { return Array.Find(Childs, w => w.Text == Text); } }

        /// <summary>Графический контекст окна</summary>
        public Graphics Canva { get { return Graphics.FromHwnd(Handle); } }

        /// <summary>Признак активности ввода для окна</summary>
        public bool Enabled
        {
            get { return User32.IsWindowEnabled(Handle); }
            set
            {
                if(!User32.EnableWindow(Handle, value))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

        /// <summary>Признак прозрачности окна [0..1]: 1 - окно не прозрачное; 0 - окно прозрачное</summary>
        public double Transparent
        {
            get
            {
                byte lv_Alpha;
                uint lv_Key;
                LWA lv_Flags;
                if(!User32.GetLayeredWindowAttributes(Handle, out lv_Key, out lv_Alpha, out lv_Flags))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                return 1d - (double)lv_Alpha / 255;
            }
            set
            {
                value = value < 0 ? 0 : value > 1 ? 1 : value;
                var lv_Alpha = (byte)((1d - value) * 255);
                var q = User32.GetWindowLong(Handle, GWL.ExStyle);
                User32.SetWindowLong(Handle, GWL.ExStyle, q ^ WS.EX_Layered);
                if(!User32.SetLayeredWindowAttributes(Handle, 0, lv_Alpha, LWA.Alpha))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            }
        }

        /// <summary>Альфа-канал окна [0..255]: 0 - окно непрозрачное; 255 - окно прозрачное</summary>
        public byte Opasity
        {
            get
            {
                byte lv_Alpha;
                uint lv_Key;
                LWA lv_Flags;
                if(!User32.GetLayeredWindowAttributes(Handle, out lv_Key, out lv_Alpha, out lv_Flags))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                return lv_Alpha;
            }
            set
            {
                var q = User32.GetWindowLong(Handle, GWL.ExStyle);
                User32.SetWindowLong(Handle, GWL.ExStyle, q ^ WS.EX_Layered);
                if(!User32.SetLayeredWindowAttributes(Handle, 0, value, LWA.Alpha))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

        /// <summary>Признак видимости окна</summary>
        public bool Visible
        {
            get { return User32.IsWindowVisible(Handle); }
            //set { throw new NotImplementedException(); }
        }

        /// <summary>Признак существования окна в системе</summary>
        public bool IsExist { get { return User32.IsWindow(Handle); } }

        /// <summary>Признак свёрнутости окна в значёк на панели задач</summary>
        public bool IsIconic { get { return User32.IsIconic(Handle); } }

        /// <summary>Признак максимизации окна во весь экран</summary>
        public bool IsZoomed { get { return User32.IsZoomed(Handle); } }

        #region Icons

        /// <summary>Малая иконка окна, полученная на основе системы сообщений</summary>
        public Icon Icon16_Message
        {
            get
            {
                var hWnd = SendMessage(WM.GETICON);
                return hWnd == IntPtr.Zero ? null : Icon.FromHandle(hWnd);
            }
        }

        /// <summary>Малая иконка окна стиля XP, полученная на основе системы сообщений</summary>
        public Icon Icon16_XP_Message
        {
            get
            {
                var hWnd = SendMessage(WM.GETICON, (IntPtr)1);
                return hWnd == IntPtr.Zero ? null : Icon.FromHandle(hWnd);
            }
        }

        /// <summary>Большая иконка окна, полученная на основе системы сообщений</summary>
        public Icon Icon32_Message
        {
            get
            {
                var hWnd = SendMessage(WM.GETICON, (IntPtr)1);
                return hWnd == IntPtr.Zero ? null : Icon.FromHandle(hWnd);
            }
        }

        /// <summary>Малая иконка окна, полученная на основе класса окна</summary>
        public Icon Icon16_ClassLong
        {
            get
            {
                var hWnd = GetClassLong(ClassLongFlags.GCLP_HICONSM);
                return hWnd == IntPtr.Zero ? null : Icon.FromHandle(hWnd);
            }
        }

        /// <summary>Большая иконка окна, полученная на основе класса окна</summary>
        public Icon Icon32_ClassLong
        {
            get
            {
                var hWnd = GetClassLong(ClassLongFlags.GCLP_HICON);
                return hWnd == IntPtr.Zero ? null : Icon.FromHandle(hWnd);
            }
        }

        /// <summary>Малая иконка окна</summary>
        public Icon Icon16 { get { return Icon16_Message ?? Icon16_XP_Message ?? Icon16_ClassLong; } }
        /// <summary>Большая иконка окна</summary>
        public Icon Icon32 { get { return Icon32_Message ?? Icon32_ClassLong; } }
        /// <summary>Иконка окна</summary>
        public Icon Icon { get { return Icon32 ?? Icon16; } }

        #endregion

        /// <summary>Изображение окна</summary>
        public Image Image
        {
            get
            {
                var rect = Rectangle;
                var bmp = new Bitmap(rect.Width, rect.Height);
                using(var DC = Graphics.FromImage(bmp))
                    if(!PrintWindow(DC, 0)) return null;
                return bmp;
            }
        }

        /// <summary>Положение окна</summary>
        public WindowPlacement Placement
        {
            get
            {
                // ReSharper disable RedundantAssignment
                var result = WindowPlacement.Default;
                // ReSharper restore RedundantAssignment
                if(!User32.GetWindowPlacement(Handle, out result))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
                return result;
            }
            set
            {
                value.Length = Marshal.SizeOf(value);
                if(!User32.SetWindowPlacement(Handle, value))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
        }

        /// <summary>Описание класса окна</summary>
        public WindowClass Class { get { return f_WindowClass.Value; } }

        public WS Style
        {
            get { return GetWindowLong(GWL.Style); }
            set { SetWindowLong(GWL.Style, value); }
        }

        /* --------------------------------------------------------------------------------------------- */

        public Window(IWin32Window window) : this(window.Handle) { }

        /// <summary>Инициализация нового окна</summary>
        /// <param name="Handle">Дескриптор окна</param>
        public Window(IntPtr Handle)
        {
            this.Handle = Handle;
            f_ThreadId = User32.GetWindowThreadProcessId(Handle, out f_ProcessId);

            f_ZOrder = new LazyValue<ZWindowsOrder>(() => new ZWindowsOrder(this));
            f_WindowClass = new LazyValue<WindowClass>(() => new WindowClass(this));
        }

        /* ------------------------------------------------------------------------------------------ */

        /// <summary>Восстанавливает окно из иконки</summary>
        /// <returns>Истина, если удалось</returns>
        public bool OpenIcon() { return User32.OpenIcon(Handle); }

        /// <summary>Назначает верхним окно системы</summary>
        /// <returns>Истина, если удалось</returns>
        public bool SetForeground() { return User32.SetForegroundWindow(Handle); }

        //public bool SetBackground() {return }

        public bool SetTopMost()
        {
            return User32.SetWindowPos(Handle, InsertAfterEnumHWND.TopMost, 0, 0, 0, 0,
                                       SetWindowPosFlags.IgnoreMove | SetWindowPosFlags.IgnoreResize);
        }

        ///// <summary>Свернуть окно</summary>
        ///// <returns>Истина, если окно свернуть удалось</returns>
        //public bool Hide() { return API.CloseWindow(Handle); }

        /// <summary>Закрыть окно</summary>
        /// <returns>Истина, если закрыть окно удалось</returns>
        public bool Close() { return SendMessage(WM.CLOSE) == IntPtr.Zero; }

        /// <summary>Закрыть окно асинхронно</summary>
        /// <returns>Истина, если команда выполнена успешно</returns>
        public bool CloseAsync() { return PostMessage(WM.CLOSE) == IntPtr.Zero; }

        /// <summary>Получить текст окна</summary>
        /// <returns>Текст окна</returns>
        private string GetWindowText()
        {
            var lv_Result = new StringBuilder(User32.GetWindowTextLength(Handle) + 1);
            if(lv_Result.Capacity > 0)
                User32.GetWindowText(Handle, lv_Result, (uint)lv_Result.Capacity);
            return lv_Result.ToString();
        }

        /// <summary>Установить текст окна</summary>
        /// <param name="Title">Новый текст</param>
        /// <returns>Истина, если удалось</returns>
        public bool SetWindowText(string Title) { return User32.SetWindowText(Handle, Title); }

        private string GetWindowClass()
        {
            //int nRet;
            var lv_ClassName = new StringBuilder(100);
            User32.GetClassName(Handle, lv_ClassName, lv_ClassName.Capacity);
            return lv_ClassName.ToString();
        }

        /// <summary>Имитация нажатия кнопки мышки в окне</summary>
        public void Click()
        {
            PostMessage(WM.LBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
            PostMessage(WM.LBUTTONUP, IntPtr.Zero, IntPtr.Zero);
        }

        /// <summary>Имитация нажатия кнопки мышки в окне</summary>
        /// <param name="Point">Точка с координатой нажатия мышки</param>
        public void Click(Point Point)
        {
            var lv_GcHandle = GCHandle.Alloc(Point);
            var lv_LParams = GCHandle.ToIntPtr(lv_GcHandle);
            PostMessage(WM.LBUTTONDOWN, IntPtr.Zero, lv_LParams);
            PostMessage(WM.LBUTTONUP, IntPtr.Zero, lv_LParams);
            lv_GcHandle.Free();
        }

        /// <summary>Имитация нажатия кнопки мышки в окне</summary>
        /// <param name="X">Положение указателя мыши по горизонтали</param>
        /// <param name="Y">Положение указателя мыши по вертикали</param>
        public void Click(int X, int Y) { Click(new Point(X, Y)); }

        /// <summary>Отправка сообщения окну</summary>
        /// <param name="Message">Отправляемое сообщение</param>
        /// <returns>Дескриптор результата</returns>
        public IntPtr SendMessage(WM Message) { return SendMessage(Message, IntPtr.Zero, IntPtr.Zero); }

        /// <summary>Отправка сообщения окну</summary>
        /// <param name="Message">Отправляемое сообщение</param>
        /// <param name="wParams">Первый конкретизируемый параметр</param>
        /// <returns>Дескриптор результата</returns>
        public IntPtr SendMessage(WM Message, IntPtr wParams) { return SendMessage(Message, wParams, IntPtr.Zero); }

        /// <summary>Отправка сообщения окну</summary>
        /// <param name="Message">Отправляемое сообщение</param>
        /// <param name="wParams">Первый конкретизируемый параметр</param>
        /// <param name="lParams">Второй конкретизируемый параметр</param>
        /// <returns>Дескриптор результата</returns>
        public IntPtr SendMessage(WM Message, IntPtr wParams, IntPtr lParams) { return User32.SendMessage(Handle, Message, wParams, lParams); }

        public IntPtr PostMessage(WM Message, IntPtr wParams, IntPtr lParams) { return User32.PostMessage(Handle, Message, wParams, lParams); }

        public IntPtr PostMessage(WM Message) { return PostMessage(Message, IntPtr.Zero, IntPtr.Zero); }

        public IntPtr SendMessageTimeout(WM Message, UIntPtr wParam, IntPtr lParam, SendMessageTimeoutFlags Flags, uint Iimeout,
            out IntPtr Result)
        {
            return User32.SendMessageTimeout(Handle, Message, wParam, lParam, Flags, Iimeout, out Result);
        }

        public int SetWindowLong(GWL gwl, WS ws)
        {
            return User32.SetWindowLong(Handle, gwl, ws);
        }

        public WS GetWindowLong(GWL gwl) { return User32.GetWindowLong(Handle, gwl); }

        public Hook SetHook(HookType type = HookType.WH_CALLWNDPROC) { return new Hook(type, ThreadId); }

        /// <summary>Минимизировать окно</summary>
        /// <returns>Истина, если операция выполнена успешно</returns>
        public bool Minimaze() { return User32.ShowWindow(Handle, ShowWindowCommands.Minimize); }
        /// <summary>Минимизировать форсированно окно даже если поток окна не отвечает</summary>
        /// <returns>Истина, если операция выполнена успешно</returns>
        public bool MinimazeForce() { return User32.ShowWindow(Handle, ShowWindowCommands.ForceMinimize); }
        /// <summary>Максимизировать окно</summary>
        /// <returns>Истина, если операция выполнена успешно</returns>
        public bool Maximize() { return User32.ShowWindow(Handle, ShowWindowCommands.Maximize); }
        /// <summary>Скрыть окно</summary>
        /// <returns>Истина, если операция выполнена успешно</returns>
        public bool Hide() { return User32.ShowWindow(Handle, ShowWindowCommands.Hide); }
        /// <summary>Активировать и показать окно</summary>
        /// <returns>Истина, если операция выполнена успешно</returns>
        public bool Normal() { return User32.ShowWindow(Handle, ShowWindowCommands.Normal); }
        /// <summary>Восстановить окно</summary>
        /// <returns>Истина, если операция выполнена успешно</returns>
        public bool Restore() { return User32.ShowWindow(Handle, ShowWindowCommands.Restore); }

        public IntPtr GetClassLong(ClassLongFlags Flags) { return User32.GetClassLong_Safe(Handle, Flags); }

        public bool PrintWindow(Graphics DC, uint Flags) { return PrintWindow(DC.GetHdc(), Flags); }
        public bool PrintWindow(IntPtr hDC, uint Flags) { return User32.PrintWindow(Handle, hDC, Flags); }

        public bool AttachThreadInput(bool Attach = true)
        {
            var CurrentThreadId = Thread.CurrentThread.ManagedThreadId;
            return AttachThreadInput(CurrentThreadId, Attach);
        }

        public bool AttachThreadInput(int ThreadId, bool Attach = true)
        {
            return User32.AttachThreadInput(ThreadId, f_ThreadId, Attach);
        }

        public bool ShowWindow(ShowWindowCommands State = ShowWindowCommands.Restore) { return User32.ShowWindow(Handle, State); }

        public bool SetWindowPos(IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags)
        {
            return User32.SetWindowPos(Handle, hWndInsertAfter, x, y, cx, cy, uFlags);
        }

        public bool SetWindowPos(InsertAfterEnum InsertAfte, int x, int y, int cx, int cy, SetWindowPosFlags uFlags)
        {
            return User32.SetWindowPos(Handle, InsertAfte, x, y, cx, cy, uFlags);
        }

        /// <summary>Зафиксировать курсор мыши в окне</summary>
        /// <returns></returns>
        public bool ClipCursor()
        {
            var lv_Rect = new RECT();
            if(!User32.GetWindowRect(Handle, ref lv_Rect))
                Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error());
            return User32.ClipCursor(lv_Rect);
        }

        public void Flash(int Count = 10, int Timeout = 500)
        {
            var state = true;
            Count <<= 1;
            for(var i = 0; i < Count; i++)
            {
                User32.FlashWindow(Handle, state);
                state = !state;
                Thread.Sleep(Timeout);
            }
            User32.FlashWindow(Handle, false);
        }

        /* --------------------------------------------------------------------------------------------- */
    }
}
